﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Xml;
using System.Xml.XPath;
using Allegro.TaskExecution;

namespace Allegro.TaskExecution
{
    public abstract partial class Task : PersistentItem, ITask, ITaskControl, ITaskDebug
    {
        static string[] defaultBreakpoints = new string[] { TaskBreakpoints.Begin, TaskBreakpoints.End };

        private ITask _parentTask;
        //private Guid _oid;
        //private string _name;
        List<ITask> _dependsOn = null;
        ManualResetEvent _completionEvent = null;
        private ITaskActivity _activity;
        /// <summary>
        /// 
        /// </summary>
        protected Dictionary<IExecutionContext, TaskExecutionProgressNotification> ProgressCallbacks = null;
        private List<string> _breakPoints;

        /// <summary>
        /// 
        /// </summary>
        protected Task()
        {
            //_oid = Guid.NewGuid();
            _dependsOn = new List<ITask>();
            _completionEvent = new ManualResetEvent(false);
            _breakPoints = new List<string>(defaultBreakpoints);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        protected Task(string name)
            : this()
        {
            Name = name;
        }
        /// <summary>
        /// 
        /// </summary>
        public ITaskActivity Activity
        {
            get
            {
                return _activity;
            }
            set
            {
                if (value != null)
                {
                    _activity = value;
                    //_activity.SpawningTask = this;
                }
            }
        }

       /// <summary>
       /// 
       /// </summary>
        public ITask RootTask
        {
            get
            {
                ITask root = null;
                if (_parentTask != null)
                    root = _parentTask.RootTask ?? _parentTask;
                return root;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        public ITask ParentTask
        {
            get { return _parentTask; }
            set { _parentTask = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public virtual bool CanExecute(IExecutionContext context)
        {
            return true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public virtual void Execute(IExecutionContext context)
        {
            ITaskContextCoordination c = context as ITaskContextCoordination;
            // Task start event
            PostEvent(context, ProgressEventType.TaskStarted, 0.0, "");

            // Pre execution
            PreExecute(c);

            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);

            // Execute the task
            ExecuteTask(context);

            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);

            // Complete the task
            c.Complete(this);

            // Task completion event
            PostEvent(context, ProgressEventType.TaskCompleted, 1.0, "");

            // Post execution
            PostExecute(c);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected virtual void ExecuteTask(IExecutionContext context)
        {

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="eventType"></param>
        /// <param name="progress"></param>
        /// <param name="message"></param>
        protected void PostEvent(IExecutionContext context, ProgressEventType eventType, double progress, string message)
        {
            if (ProgressCallbacks == null)
                return;
            foreach (TaskExecutionProgressNotification callback in ProgressCallbacks.Values)
            {
                callback(this, context, eventType, progress, message);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="originatingTask"></param>
        /// <param name="context"></param>
        /// <param name="eventType"></param>
        /// <param name="progress"></param>
        /// <param name="message"></param>
        protected void PostEvent(ITask originatingTask, IExecutionContext context, ProgressEventType eventType, double progress, string message)
        {
            if (ProgressCallbacks == null)
                return;
            foreach (TaskExecutionProgressNotification callback in ProgressCallbacks.Values)
            {
                callback(originatingTask, context, eventType, progress, message);
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="task"></param>
        public void DependsOn(ITask task)
        {
            if (!_dependsOn.Contains(task))
                _dependsOn.Add(task);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="context"></param>
        public virtual void NotifyProgress(TaskExecutionProgressNotification callback, IExecutionContext context)
        {
            if (ProgressCallbacks == null)
                ProgressCallbacks = new Dictionary<IExecutionContext, TaskExecutionProgressNotification>();
            if (!ProgressCallbacks.ContainsKey(context))
            {
                ProgressCallbacks.Add(context, callback);
                context.ProgressAggregator = new ProgressAggregator();
            }
            else
            {
                string msg = "Unable to register duplicate callbacks for the same execution context";
                Log.Error(msg, this, LogEventTypes.TaskEvent);
                throw new ApplicationException(msg);
            }
        }

        /// <summary>
        /// Get the completion event for the task
        /// </summary>
        public EventWaitHandle CompletionEvent(ITaskContextCoordination context)
        {
            return context.CompletionEvent(this);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public virtual void PreExecute(ITaskContextCoordination context)
        {
            foreach (ITaskControl t in _dependsOn)
            {
                EventWaitHandle eventHandle = t.CompletionEvent(context);
                if (eventHandle != null)
                    eventHandle.WaitOne();
            }

            // Check if the breakpoint is set
            BreakIfBreakpointSet(TaskBreakpoints.Begin, context);

            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public virtual void PostExecute(ITaskContextCoordination context)
        {
            // Check if the breakpoint is set
            BreakIfBreakpointSet(TaskBreakpoints.End, context);
            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);
        }
        /// <summary>
        /// Compute progress
        /// </summary>
        /// <param name="context"></param>
        public virtual void ComputeProgress(IExecutionContext context)
        {
            IExecutionProgressAggregator progressAggregator = context.ProgressAggregator;
            if (progressAggregator == null)
                return;
            double aggregatedProgress = 0;
            if (this is ICompositeTask)
            {
                ICompositeTask compositeTask = this as ICompositeTask;
                double nTasks = compositeTask.Tasks.Count;
                if (nTasks == 0)
                {
                    //string msg = string.Format("Composite task {0} has no children. Unable to compute progress", Name);
                    //Log.Error(msg, this, LogEventTypes.TaskEvent);
                    //throw new ApplicationException(msg);
                    aggregatedProgress = 1.0;
                }
                else
                {
                    double weightTotal = 0.0;
                    // Calculate progress
                    var tasks = compositeTask.Tasks;
                    Monitor.Enter(tasks);
                    var tasklist = new List<ITask>(tasks);
                    Monitor.Exit(tasks);
                    foreach (ITask child in tasklist)
                    {
                        double taskProgress = progressAggregator.GetProgress(child);
                        double weight = compositeTask.GetEstimatedDuration(child);
                        weightTotal += weight;
                        aggregatedProgress += taskProgress * weight;
                    }
                    aggregatedProgress /= weightTotal;
                }
                progressAggregator.UpdateProgress(this, aggregatedProgress, progressAggregator.ProgressMessage);
            }
            else
            {
                aggregatedProgress = progressAggregator.GetProgress(this);
            }
            if (ParentTask != null && ParentTask is ITaskControl)
            {
                // If this task is part of a task hierarchy, walk the hierarchy and recompute progress
                IExecutionContext aggregationContext = null;
                if (context is ITaskContextDerivation)
                {
                    aggregationContext = (context as ITaskContextDerivation).OuterContext;
                }
                if (aggregationContext == null)
                    aggregationContext = context;
                (ParentTask as ITaskControl).ComputeProgress(aggregationContext);
            }
            else
            {
                // Notify the caller if a callback is set
                if (ProgressCallbacks != null && ProgressCallbacks.ContainsKey(context))
                {
                    ProgressCallbacks[context](this, context, ProgressEventType.TaskProgress, aggregatedProgress, progressAggregator.ProgressMessage);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            if (Activity != null)
            {
                writer.WriteStartElement("Activity", XmlNamespace);
                XmlPersist.SaveObject(writer, Activity, persistenceContext);
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            persistenceContext.NamespaceManager.AddNamespace(Constants.XmlDefaultNamespacePrefix, Constants.XmlNamespace);

            XPathNavigator activityNode = itemNode.SelectSingleNode("al:Activity", persistenceContext.NamespaceManager);
            if (activityNode != null)
            {
                activityNode.MoveToFirstChild();
                if (activityNode != null)
                    Activity = XmlPersist.LoadObject<ITaskActivity>(activityNode);
            }
        }
        //static public ITask LoadTask(XPathNavigator navigator, IXmlPersistenceContext persistenceContext)
        //{
        //    string taskId = navigator.Name;
        //    Task task = null;
        //    //switch (taskId)
        //    //{
        //    //    case TaskTypes.ODEIntegrationTask:
        //    //        {
        //    //            task = new ODEIntegrationTask();
        //    //        }
        //    //        break;
        //    //    case TaskTypes.RunInParallel:
        //    //        {
        //    //            task = new RunInParallel();
        //    //        }
        //    //        break;
        //    //    case TaskTypes.TaskSequence:
        //    //        {
        //    //            task = new RunInSequence();
        //    //        }
        //    //        break;
        //    //    //case TaskTypes.CodedTask:
        //    //    //    {
        //    //    //        XPathNavigator classRefNode = navigator.SelectSingleNode("@classRef");
        //    //    //        if (classRefNode != null)
        //    //    //        {
        //    //    //            string classRef = classRefNode.Value;
        //    //    //            Type t = Type.GetType(classRef);
        //    //    //            if (t != null)
        //    //    //            {
        //    //    //                task = Activator.CreateInstance(t) as Task;
        //    //    //            }
        //    //    //            else
        //    //    //            {
        //    //    //                ErrorHandler.Error(string.Format("Failed to instantiate coded task {0}", classRef));
        //    //    //            }
        //    //    //        }
        //    //    //    }
        //    //    //    break;
        //    //    case TaskTypes.ParameterVariationTask:
        //    //        {
        //    //            task = new ParameterVariationTask();
        //    //        }
        //    //        break;
        //    //    default:
        //    //        {
        //    //            XPathNavigator classRefNode = navigator.SelectSingleNode("@classRef");
        //    //            if (classRefNode != null)
        //    //            {
        //    //                string classRef = classRefNode.Value;
        //    //                Type t = Type.GetType(classRef);
        //    //                if (t != null)
        //    //                {
        //    //                    task = Activator.CreateInstance(t) as Task;
        //    //                }
        //    //                else
        //    //                {
        //    //                    ErrorHandler.Error(string.Format("Failed to instantiate coded task {0}", classRef));
        //    //                }
        //    //            }
        //    //            else
        //    //            {
        //    //                string ns = navigator.NamespaceURI;
        //    //                if (!string.IsNullOrEmpty(ns))
        //    //                {
        //    //                    string localName = navigator.LocalName;
        //    //                    string prefix = navigator.Prefix;
        //    //                    if (ns.Contains("clr-namespace:"))
        //    //                    {
        //    //                        int i = ns.IndexOf(":");
        //    //                        string clrNamespace = ns.Substring(i + 1);
        //    //                        string typeName = string.Format("{0}.{1}", clrNamespace, localName);
        //    //                        Type type = Type.GetType(typeName);
        //    //                        if (type == null)
        //    //                        {
        //    //                            AppDomain currentAppDomain = AppDomain.CurrentDomain;
        //    //                            Assembly[] assemblies = currentAppDomain.GetAssemblies();
        //    //                            foreach (Assembly assembly in assemblies)
        //    //                            {
        //    //                                type = assembly.GetType(typeName);
        //    //                                if (type != null)
        //    //                                    break;
        //    //                            }
        //    //                        }
        //    //                        if (type != null)
        //    //                        {
        //    //                            task = Activator.CreateInstance(type) as Task;
        //    //                        }
        //    //                    }
        //    //                }
        //    //            }
        //    //        }
        //    //        break;
        //    //}
        //    //if (task != null)
        //    //{
        //    //    task.Load(taskManager, navigator);
        //    //    if (taskManager != null)
        //    //    {
        //    //        taskManager.Registry.RegisterItem(task);
        //    //        task.TaskManager = taskManager;
        //    //    }
        //    //}
        //    return task;
        //}

        // Protected methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        protected static void RunTask(object input)
        {
            TaskExecutionRecord taskContext = input as TaskExecutionRecord;
            ITask t = taskContext.Task;
            ITaskControl control = t as ITaskControl;
            ITaskContextCoordination context = taskContext.Context as ITaskContextCoordination;
            //if (control != null && context != null)
            //    control.PreExecute(context);
            try
            {
                t.Execute(context);
            }
            catch (AbortedTaskExecutionException x)
            {
                string msg = string.Format(
                                            "_RunTask - aborted task {0} on thread {1}. Exception message: {2}. Stack trace: {3}",
                                            t.Name, Thread.CurrentThread.ManagedThreadId, x.Message, x.StackTrace);
                Console.WriteLine(msg);
                Log.Error(msg, control, LogEventTypes.TaskEvent);
                // Must complete the job
                context.Complete(t);
                //throw (x);
            }
            catch (Exception x)
            {
                if (control != null)
                {
                    //control.CompletionEvent.Set();

                }
                string msg = string.Format(
                            "_RunTask - aborted task {0} on thread {1}. Exception message: {2}. Stack trace: {3}",
                            t.Name, Thread.CurrentThread.ManagedThreadId, x.Message, x.StackTrace);
                Console.WriteLine(msg);
                Log.Error(msg, control, LogEventTypes.TaskEvent);
                //throw new ApplicationException(msg, x);
            }
            if (control != null)
            {
                //control.PostExecute(context);
                //context.Complete(t);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected void CheckIfSuspendOrAbort(IExecutionContext context)
        {
            ITaskContextCoordination control = context as ITaskContextCoordination;
            if (control != null)
            {
                control.ExitIfJobIsAborted();
                control.WaitIfJobIsSuspended();
            }
        }
        /// <summary>
        /// Check if 
        /// </summary>
        /// <param name="breakpointName"></param>
        /// <param name="context"></param>
        protected void BreakIfBreakpointSet(string breakpointName, IExecutionContext context)
        {
            if(context is ITaskExecutionControl)
            {
                ITaskExecutionControl control = context as ITaskExecutionControl;
                if (control.BreakpointIsSet(Oid.ToString(), breakpointName))
                {
                    // Suspend
                    control.SuspendTaskExecution();
                    // Post an event notifying that the task has breaked execution
                    PostEvent(this, context, ProgressEventType.TaskBreaked, 0.0, "");
                    Console.WriteLine("Breakpoint {0} hit for task {1}", breakpointName, Oid);
                    // Wait
                    CheckForBreakpoint(context);
                    Console.WriteLine("Task {0} continuing after break on breakpoint {1}", Oid, breakpointName);
                    // Post event notifying that the task resumes execution
                    PostEvent(this, context, ProgressEventType.TaskResumed, 0.0, "");
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        protected void CheckForBreakpoint(IExecutionContext context)
        {
            ITaskContextCoordination control = context as ITaskContextCoordination;
            if (control != null)
            {
                control.WaitIfJobIsSuspended();
            }
        }
        /// <summary>
        /// If the current context has a derived context, return the derived context.
        /// Otherwise, return the current context 
        /// </summary>
        /// <param name="contextKey"></param>
        /// <param name="currentContext"></param>
        /// <returns></returns>
        protected IExecutionContext GetTaskExecutionContext(object contextKey, IExecutionContext currentContext)
        {
            // Navigate to the task context
            IExecutionContext executionContext = null;
            if (currentContext is ITaskContextDerivation)
            {
                ITaskContextDerivation contextDerivation = currentContext as ITaskContextDerivation;
                if (contextDerivation.DerivedTaskContexts.ContainsKey(contextKey))
                    executionContext = contextDerivation.DerivedTaskContexts[contextKey];
            }
            if (executionContext == null)
                executionContext = currentContext;
            return executionContext;
        }
        /// <summary>
        /// Search for the context in the derived context tree below the current task. If no associated 
        /// context is found, return the current context.
        /// </summary>
        /// <param name="contextKey"></param>
        /// <param name="currentContext"></param>
        /// <returns></returns>
        protected IExecutionContext FindTaskExecutionContext(object contextKey, IExecutionContext currentContext)
        {
            // Navigate to the task context
            IExecutionContext executionContext = null;
            if (currentContext is ITaskContextDerivation)
            {
                ITaskContextDerivation contextDerivation = currentContext as ITaskContextDerivation;
                executionContext = contextDerivation.FindTaskExecutionContext(contextKey);
            }
            else
            {
                executionContext = currentContext;
            }

            return executionContext;
        }

        #region Implementation of ITaskDebug

        /// <summary>
        /// Breakpoints
        /// </summary>
        public IList<string> BreakPoints
        {
            get { return _breakPoints; }
        }

        #endregion
    }
}
